package ro.sergiu.properties;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ro.sergiu.properties.annotations.ConfigSource;
import ro.sergiu.properties.annotations.ConfigSourceType;
import ro.sergiu.properties.annotations.Ignore;
import ro.sergiu.properties.annotations.PropertyName;

/**
 *
 * @author Toporjinschi
 */
public final class ConfigContainer {

    private static final String SET = "set";
    private static final String PLACEHOLDER_REGEX = "\\$\\{(.*?)\\}";
    private static final Map<Class, Object> CONFIGS = new HashMap<Class, Object>();

    public static void loadConfigurations(Class clazz) throws InstantiationException, IllegalAccessException, IOException {
        ConfigSource source = (ConfigSource) clazz.getAnnotation(ConfigSource.class);
        if (source.type() == ConfigSourceType.FILE) {
            File file = getCofigFile(source.value());
            if (!file.exists() || !file.canRead()) {
                throw new RuntimeException("Could not read the file " + source.value());
            }
            CONFIGS.put(clazz, injectValues(clazz, file));
        }
    }

    private static File getCofigFile(String filePath) {
        Pattern p = Pattern.compile(PLACEHOLDER_REGEX);
        Matcher m = p.matcher(filePath);
        while (m.find()) {
            String placeHolder = m.group();
            String systemVar = placeHolder.substring(2, placeHolder.indexOf("}"));
            String value = System.getProperty(systemVar) == null ? "" : System.getProperty(systemVar);
            filePath = filePath.replace(placeHolder, value);
        }
        return new File(filePath);
    }

    private static Object injectValues(Class clazz, File file) throws InstantiationException, IllegalAccessException, IOException {
        Object config = clazz.newInstance();
        Properties prop = new Properties();
        prop.load(new FileReader(file));
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            if (field.isAnnotationPresent(Ignore.class)) {
                continue;
            }
            String fieldName = field.getName();
            String value;
            if (field.isAnnotationPresent(PropertyName.class)) {
                value = prop.getProperty(field.getAnnotation(PropertyName.class).value());
            } else {
                value = prop.getProperty(fieldName);
            }
            if (value == null) {
                continue;
            }
            Class<?> type = field.getType();
            injectFieldValue(fieldName, value, type, clazz, config);
        }
        return config;
    }

    private static void injectFieldValue(String name, String value, Class<?> type, Class<?> clazz, Object o) {
        String methodName = SET + name.substring(0, 1).toUpperCase() + name.substring(1);
        try {
            Method method = clazz.getMethod(methodName, type);
            if (type == String.class) {
                method.invoke(o, value);
            } else if (type == Integer.class) {
                method.invoke(o, Integer.parseInt(value));
            } else if (type == Float.class) {
                method.invoke(o, Float.parseFloat(value));
            } else if (type == Double.class) {
                method.invoke(o, Double.parseDouble(value));
            } else if (type == Boolean.class) {
                method.invoke(o, Boolean.parseBoolean(value));
            }
        } catch (NoSuchMethodException ex) {
            Logger.getLogger(ConfigContainer.class.getName()).log(Level.WARNING, "Property " + name + " ignored", ex);
        } catch (SecurityException ex) {
            Logger.getLogger(ConfigContainer.class.getName()).log(Level.WARNING, "Property " + name + " ignored", ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(ConfigContainer.class.getName()).log(Level.WARNING, "Property " + name + " ignored", ex);
        } catch (IllegalArgumentException ex) {
            Logger.getLogger(ConfigContainer.class.getName()).log(Level.WARNING, "Property " + name + " ignored", ex);
        } catch (InvocationTargetException ex) {
            Logger.getLogger(ConfigContainer.class.getName()).log(Level.WARNING, "Property " + name + " ignored", ex);
        }
    }

    public static <T> T getConfig(Class<T> x) {
        return (T) CONFIGS.get(x);
    }
}
